#include "ServerCommands.h"
#include "const.h"
#include "SepararString.h"
#include "StreamBloqueante.h"
#include "MD5.h"
#include "ObtenerContenidosArchivo.h"

#include "FileDateModification.h"
#include "MetadataHandler.h"

#include "ReceptorDelta.h"
#include "EmisorDelta.h"
#include "DatosTrasmitidos.h"

#include "HexABin.h"

#include <fstream>
#include <errno.h>
#include <algorithm>

void ServerCommand::parametros(std::string p){ params = p;}

std::string GetFilesCommand::execute(std::string &username){
	// Cargo todos los datos a enviar.
	MetadataHandler handler(username);
	std::string aux = handler.getFileList();

	// Variable que almacenara el mensaje a ser enviado
	// al cliente.
	std::string msj(FILELIST);
	msj += " ";
	msj += aux;

	return msj;
}

std::string GetFileCommand::execute(std::string &username){

	// Obtengo los parametros a partir del string s.
	std::vector<string> vec;
	separarString(params, ' ', vec);
	std::string path = USRDIR + username + "/" + vec[0];

	std::string msj = obtenerContenidosArchivo(path.c_str());

	return msj;
}

std::string UploadFileCommand::execute(std::string &username){

	// Obtengo los parametros a partir del string s.
	int pos1 = params.find_first_of(' ');
	int pos2 = params.find_first_of(' ', pos1 + 1);
	std::string nombreArch = params.substr(0, pos1);
	std::string fecha = params.substr(pos1 + 1, pos2 - pos1 - 1);
	std::string d = params.substr(pos2 + 1);

	// Creo el nuevo archivo con los datos recibidos.
	std::string path;
	path = USRDIR + username + "/" + nombreArch;
	StreamBloqueante arch(path, WRITE);

	uint8_t * datos = new uint8_t [d.size()  / 2];
	hexABin(d, datos);

	arch.stream().write((char*)datos, d.size()  / 2);
	arch.close();
	delete[] datos;

	//Obtengo el hash del archivo.
	std::string hash = md5(d);
	std::string oldHash = NOOLDHASH;

	// Agrego la data del nuevo archivo.
	MetadataArchivo dataArch(nombreArch, hash, oldHash, fecha);
	MetadataHandler handler(username);
	handler.agregar(dataArch);

	std::string msj;
	msj = NOTIFY;
	msj += " ";
	msj += NEW;
	msj += " " + nombreArch;
	return msj;
}

std::string TryUpdateUpFileCommand::execute(std::string &username){

	// Parsea los parametros.
	std::vector<string> vec;
	separarString(params, ' ', vec);
	std::string nombreArchivo = vec[0];
	std::string hashViejo = vec[1];

	std::string msj;
	MetadataHandler handler(username);
	MetadataArchivo dataArch = handler.encontrar(nombreArchivo);
	if ( dataArch.compararConHash(hashViejo) != 0 ) msj = LOGINFAIL;
	else msj = LOGINOK;

	return msj;
}

std::string TryUpdateDownFileCommand::execute(std::string &username){

	// Parsea los parametros.
	std::vector<string> vec;
	separarString(params, ' ', vec);
	std::string nombreArchivo = vec[0];
	std::string hash = vec[1];

	std::string msj;
	MetadataHandler handler(username);
	MetadataArchivo dataArch = handler.encontrar(nombreArchivo);
	if ( dataArch.compararConHashViejo(hash) != 0 ) msj = LOGINFAIL;
	else msj = LOGINOK;

	return msj;
}

std::string UpdateFileCommand::execute(std::string &username){

	// Parsea los parametros.
	std::vector<string> vec;
	separarString(params, ' ', vec);

	std::string nombreArchivo = vec[0];
	FileDateModification fecha;
	stringstream aux(vec[1]);
	aux >> fecha;

	MetadataHandler handler(username);

	// Obtiene la metadata correspondiente al archivo modificado.
	MetadataArchivo dataArch = handler.encontrar(nombreArchivo);
	if (dataArch.fail()) throw FileDoesntExistException();

	// Determina quien es el emisor y quien el receptor comparando las fechas.
	std::string msj(DELTA); msj += " ";
	if( dataArch.compararConFecha(fecha) > 0 ) msj += RECEPTORDELTA;
	else {
		msj += EMISORDELTA;
		msj += " "; msj += vec[1];
	}
	return msj;
}

std::string UpdateFileEmisorCommand::execute(std::string &username){

	// Parsea los parametros.
	// Recibo paquetes de hashes (MD5 y ADLER32 de bloques no solapados)
	int pos = params.find_first_of(' ');
	std::string nombreArchivo = params.substr(0, pos);
	std::string datos = params.substr(pos + 1);

	// Genero el path al archivo y creo el EmisorDelta, que se encargara de manejar
	// la actualizacion del archivo.
	std::string path(USRDIR);
	path += username;
	path += "/";
	path += nombreArchivo;

	// El server es emisor del delta
	EmisorDelta emisor;
	emisor.Iniciar(path.c_str());

	// Emisor calcular los rolling checksum del file en comun
	emisor.CalcularHashesVarios();

	// Serializamos datos transmitidos
	DatosTrasmitidos<PaqueteHashReceptor> hashesReceptor(datos);
	// Lista de los cambios diferenciales en los archivos
	DatosTrasmitidos<Diferencial> datosTransmitidos =
			emisor.compararConHashesDelReceptor(hashesReceptor);

	// Serializo todos los diferenciales
	std::string diferencialesSerializados = datosTransmitidos.Serializar();

	return diferencialesSerializados;
}

std::string UpdateFileReceptorCommand::execute(std::string &username){

	std::cout << "Iniciando la recepcion del delta: voy a enviar mis datos locales\n";
	// Genero el path al archivo y creo el EmisorDelta, que se encargara de manejar
	// la actualizacion del archivo.
	std::string path(USRDIR);
	path += username;
	path += "/";
	path += params;

	// El server es receptor del delta
	ReceptorDelta receptor;
	receptor.Iniciar(path.c_str());
	receptor.CalcularHashesVarios();
	std::string serializacion = receptor.Serializar();

	return serializacion;
}

std::string UpdateFileReceptor2Command::execute(std::string &username){

	// Parsea los parametros.
	// Recibo paquetes de hashes (MD5 y ADLER32 de bloques no solapados)
	int pos1 = params.find_first_of(' ');
	int pos2 = params.find_first_of(' ', pos1 + 1);
	std::string nombreArchivo = params.substr(0, pos1);
	std::string fecha = params.substr(pos1 + 1, pos2 - pos1 - 1);
	std::string datos = params.substr(pos2 + 1);

	// Genero el path al archivo y creo el EmisorDelta, que se encargara de manejar
	// la actualizacion del archivo.
	std::string path(USRDIR);
	path += username;
	path += "/";
	path += nombreArchivo;

	// El server es receptor del delta
	ReceptorDelta receptor;
	receptor.Iniciar(path.c_str());
	receptor.CalcularHashesVarios();

	// Sincroniza el archivo.
	DatosTrasmitidos<Diferencial> datosTransmitidos(datos);
	receptor.UpdatearArchivoRespectoAemisor(datos);

	// Actualiza el indice del server.
	MetadataHandler handler(username);

	//Obtengo el hash del archivo.
	std::string aux = obtenerContenidosArchivo(path.c_str());
	std::string hash = md5(aux);

	MetadataArchivo dataArchVieja = handler.encontrar(nombreArchivo);
	MetadataArchivo dataArch(nombreArchivo, hash, dataArchVieja.hash, fecha);
	handler.modificar(dataArch);

	return SINRESPUESTA;
}

std::string UpdateFileFinishedCommand::execute(std::string &username){
	std::string msj = NOTIFY;
	msj += " ";
	msj += UPDATED;
	msj += " ";
	msj += params;
	return msj;
}


std::string EraseFileCommand::execute(std::string &username){

	// Obtengo los parametros a partir del string s.
	std::vector<string> vec;
	separarString(params, ' ', vec);
	std::string path;
	path = USRDIR + username + "/" + vec[0];
	if (remove(path.c_str()) != 0) std::cout << "no se pudo remover el archivo: " << strerror(errno);

	path.clear();
	path = USRDIR +username + USRFILES;
	StreamBloqueante metadata(path, READ);

	// Variable que almacenara el contenido del archivo metadata luego de extraer
	// el archivo actual.
	std::string msj;

	bool salteoPrimero = false;
	std::string aux;
	getline(metadata.stream(), aux);
	int pos = aux.find_first_of(' ');
	if(aux.substr(0, pos) != vec[0]) msj += aux;
	else salteoPrimero = true;
	// Cargo todos los datos a enviar.
	// TODO: los archivos ordenados por nombre.
	while (getline(metadata.stream(), aux)){
		int pos = aux.find_first_of(' ');
		if(aux.substr(0, pos) != vec[0]){
			if (!salteoPrimero) msj += "\n";
			else salteoPrimero = false;
			msj += aux;
		}
		aux.clear();
	}
	metadata.close();
	StreamBloqueante data(path,WRITE);
	data.stream() << msj;
	data.close();

	msj.clear();
	msj = NOTIFY;
	msj += " ";
	msj += ERASED;
	msj += " " + vec[0];
	return msj;
}

std::string LogoutCommand::execute(std::string &username){
	return LOGOUT;
}
